package icasue.base.register;

import icasue.base.utils.bind.SpringBootBindUtil;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.StandardAnnotationMetadata;

import java.lang.reflect.Method;
import java.util.*;

/**
 * @Author: Qiao Hang
 * @CreateDate: 2021/11/30 上午11:04
 * @UpdateDate:
 * @Description: 该实例默认由Plugin-Base内部创建，使用SPI的方式加载多个ICasuePluginRegister实例并执行注册逻辑.
 */
public class ICasuePluginsBeanDefinitionRegistryPostProcessor implements
        BeanDefinitionRegistryPostProcessor ,
        ResourceLoaderAware,
        EnvironmentAware,
        BeanFactoryAware,
        BeanClassLoaderAware {

    public BeanFactory beanFactory;
    public Environment environment;
    public ResourceLoader resourceLoader;
    public ClassLoader classLoader;
    private final List<ICasuePluginsRegister> serviceLoadedCache = new ArrayList<>();
    private final Map<Class, Object> configPropertiesLoaded = new HashMap<>();



    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

        ServiceLoader<ICasuePluginsRegister> serviceLoaded = ServiceLoader.load(ICasuePluginsRegister.class);
        for (ICasuePluginsRegister iCasuePluginsRegister : serviceLoaded) {
            readyEnvironment(iCasuePluginsRegister);
            parseEnvironmentProperties(iCasuePluginsRegister);

            Boolean check = iCasuePluginsRegister.checkRegister();
            if(check != null && check){
                serviceLoadedCache.add(iCasuePluginsRegister);
            }
        }

        AnnotationMetadata standardAnnotationMetadata;
        try {
            Method from = StandardAnnotationMetadata.class.getDeclaredMethod("from", Class.class);
            from.setAccessible(true);
            standardAnnotationMetadata = (AnnotationMetadata)from.invoke(null, Object.class);
        } catch (Throwable e) {
            throw new RuntimeException("Create AnnotationMetadata failed, details: " + e.getMessage(), e.getCause());
        }

        serviceLoadedCache.sort((left, right) -> Integer.compare(left.order(), right.order()));
        serviceLoadedCache.forEach(registerService -> {
            registerService.previousRegister();
            registerService.registerBeanDefinitions(standardAnnotationMetadata, registry);
            registerService.postRegister();
        });
    }


    private void parseEnvironmentProperties(ICasuePluginsRegister iCasuePluginsRegister){
        String environmentPrefix = iCasuePluginsRegister.environmentPropertiesPrefix();
        Class environmentPropertiesType = iCasuePluginsRegister.environmentPropertiesType();
        if (Strings.isNotBlank(environmentPrefix) && environmentPropertiesType != null){
            Object bindResult = SpringBootBindUtil.bind(environment, environmentPrefix, environmentPropertiesType);
            if(bindResult != null){
                configPropertiesLoaded.put(environmentPropertiesType, bindResult);
                iCasuePluginsRegister.setEnvironmentProperties(bindResult);
            }
        }
    }


    private void readyEnvironment(ICasuePluginsRegister iCasuePluginsRegister){
        iCasuePluginsRegister.setBeanClassLoader(classLoader);
        iCasuePluginsRegister.setResourceLoader(resourceLoader);
        iCasuePluginsRegister.setEnvironment(environment);
        iCasuePluginsRegister.setBeanFactory(beanFactory);
    }


    public <ConfigProperties> ConfigProperties readConfigProperties(Class<ConfigProperties> configPropertiesType){
        Object configProperties = configPropertiesLoaded.get(configPropertiesType);
        if(configProperties == null || !configPropertiesType.isInstance(configProperties)){
            return null;
        }
        return configPropertiesType.cast(configProperties);
    }


    @Override
    public final void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }
    @Override
    public final void setEnvironment(Environment environment) {
        this.environment = environment;
    }
    @Override
    public final void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public final void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        // Using BeanFactory supply.
    }
}
